Skip to content

[Perf] Faster block tree (de)serialization - #3349

Merged
Antonio95 merged 2 commits into
stagingfrom
perf/faster_block_tree_deser
Aug 3, 2026
Merged

[Perf] Faster block tree (de)serialization#3349
Antonio95 merged 2 commits into
stagingfrom
perf/faster_block_tree_deser

Conversation

@ljedrz

@ljedrz ljedrz commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Found while working on ProvableHQ/snarkOS#4361: restarting the nodes took way longer than I anticipated, and it boils down to a flat ~48s (on my machine, ~125s in the CI) cost of loading the block tree cache, which - while still a big improvement for very large ledgers - is a slowdown for small ones.

This PR removes this flat (de)serialization "tax", by introducing a MerkleTreeState object, which is a subset of the MerkleTree, sans its hashers, which can be recreated without having to deserialize them from the cache file.

Below is a comparison of the block tree load times between the current state and this PR:

leaves from scratch legacy file legacy load new file new load
1 0.35 ms 21,712,808 B 48.07 s 112 B 0.34 ms
100 2.85 ms 21,720,040 B ~48 s 7,344 B 0.28 ms
10,000 19.4 ms 22,557,032 B ~48 s 844,336 B 1.34 ms
1,000,000 1.295 s 87,267,176 B ~48 s 65,554,480 B 97.7 ms

Why haven't we noticed this sooner? Variable window size was added to BHP on 30.04.2026 (dd09be7, efa368b), along with setup and size metrics, which increased the size of the serialized block tree from ~210KiB to 20.7MiB, so what originally cost ~0.5s to deserialize now costs ~48s. Note: those changes were most likely expected, and have their own benefits, with this fallout just being an expected side effect.

ljedrz added 2 commits July 31, 2026 13:00
Signed-off-by: ljedrz <ljedrz@users.noreply.github.com>
Signed-off-by: ljedrz <ljedrz@users.noreply.github.com>
@ljedrz
ljedrz requested review from Antonio95 and vicsn July 31, 2026 11:21

@vicsn vicsn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent!

@Antonio95 Antonio95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great stuff 👌 I think the reason the issue hadn't been caught earlier is that the changes which caused it were merged into staging only recently (#3249).

I have left only minor comment regarding the consistency check in from_state. Happy to approve once we discuss that one.

}
ensure!(root_hash == root, "The Merkle tree state has an invalid root");

Ok(Self {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I gather, this initialisation function checks consistency between the "internal root" tree[0] (the root of the largest subtree including all non-empty leaves) and the full tree's root (obtained by hashing the previous value with the empty hash a number of times). But it does not check consistency between tree (the digests of the implicit leaves) and the internal root.

Is my understanding correct? And if so, is there any particular reason for that half-check design? It seems it would make more sense to either check consistency in both cases, or in neither - but I might be missing something.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the same logic as in the existing prepare_append (L#343), just double-checked against the state's root for internal consistency, since we only ever use it against our own cached tree (i.e. we trust it); strictly speaking, we could skip it, but it's relatively cheap

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, so it's a cheap sanity check 👌 And doing the whole digests-to-tree[0] check is not justified in terms of time vs. necessity. Approving now.

@Antonio95
Antonio95 merged commit d365bc4 into staging Aug 3, 2026
7 checks passed
@Antonio95
Antonio95 deleted the perf/faster_block_tree_deser branch August 3, 2026 12:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants